/*
 * @(#)FileDumper.java	2008/07/28
 *
 * Copyright 2008 Web & Zen Multimedia, All rights reserved.
 * Use is subject to license terms.
 */
package it.wzm.lightweightlog;

import java.io.BufferedWriter;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import java.nio.channels.*;
import java.io.*;

/**
 * <p>This class should be subclassed to create other file dumper, for example xml,
 *  xls...etc.<br>
 *  As it is, it just creates a text file.
 * </p>
 * <p>
 *  This is a listing of all the properties needed, all of them must be prefixed
 *  following the rule of {@link Dumper}:
 * <ul>
 *  <li>url = where the file is saved</li>
 *  <li>rotation = the type of rotation, only SIZE is allowed in this release</li>
 *  <li>size = the size when the file need to be rotated, 10000 means 10Kb</li>
 * </ul>
 * </p>
 * <p>
 *  This class use a buffered writer to dump Entry to the file. When the data are
 *  written the class first check the file existance, if not exists (at startup,
 *  or when it is rotated) then it is created.<br>
 *  The checking of the rotation happens after the dumping of all entry is done.
 * </p>
 */
class FileDumper extends Dumper {

    /**
     * needed for serialization
     */
    static final long serialVersionUID = 20080618L;

    /**
     * rotation type for a file dumper
     */
    public enum Rotation {

        SIZE((short) 0, "size"),
        DATE((short) 1, "date");
        short type;  // for use in switch or whatever...type
        String name; // human readable name

        Rotation(short type, String name) {
            this.type = type;
            this.name = name.toUpperCase();
        }
        // getter for the variable above
        short getType() {
            return this.type;
        }

        String getName() {
            return this.name;
        }
    }

    /**
     * type of rotation based on date
     */
    public enum DateRule {

        HOURLY,
        DAILY,
        WEEKLY,
        MONTHLY,
        YEARLY,
    }
    /**
     * The main stream for writing to file
     */
    private BufferedWriter bw;
    /**
     * the path of the file
     */
    private String url;
    /**
     * type of rotation
     */
    private Rotation rotation = Rotation.SIZE;
    /**
     * the size of the rotation
     */
    private long size;
    /**
     * the file itself
     */
    private File f;
    /**
     * When we need to rotate
     */
    private boolean rotate;
    /**
     * When to check for rotation...millisec...
     */
    private long checkRotationInterval;
    /**
     * 
     */
    private Thread t;
    
    private FileChannel ch;

    /**
     * Construct a FileDumper with a given set of properties
     * @param p Properties
     */
    public FileDumper(Properties p) {
        // super constructor...
        super(p);
    }
    
    /**
     * Adding our custom property
     */
    @Override
    protected void postinit() {
        try {
            // where this file is, we also 
            // perform a check if we can write the file...
            this.url = this.get("url");
            this.f = new File(this.url);
            //if (!f.canWrite()) {
            //    throw new Exception("Cannot write " + this.url);
             //}
            
            // no need to rotate for the moment...
            this.rotate = false;
            this.checkRotationInterval = 10000;

            // determine rotation type with default to size if null
            if (this.get("rotation") != null) {
                rotation = Rotation.valueOf(
                        (this.get("rotation")).toUpperCase());

                // loading different property depending on rotation type
                switch (rotation) {
                    // rotation by size
                    case SIZE:
                        // maximum size before rotating
                        if (this.get("size") != null) {
                            this.size = Long.parseLong(this.get("size"));
                        } else {
                            this.size = DEFAULT_SIZE;
                        }
                        break;
                    /* To be implemented
                    case DATE:
                    // check the date...because rotation by date is not
                    // default, user has to specify all the parameter
                    // that's why we are not catching and setting any errors
                    break;
                     */
                }

            } else {
                // setting default rotation to size
                // with dfault rotation size
                this.rotation = Rotation.SIZE;
                this.size = DEFAULT_SIZE;
            }
            
            // little thread for checking if we need to rotate
            // our file, it is declared as inner class because it is going to modify 
            // the status and some other private/protected variable of this dumper
            Runnable r = new Runnable() {
                
                // implementing the run interface
                // as requested by the Runnable interface
                public void run() {
                    checker:
                    while (true) {
                        try {
                        	//Log.serviceLog(f.length());
                            // check if we are over rotation size
                            switch (rotation) {
                                // rotation type by size
                                case SIZE:
                                    // we first check the size
                                    if (f.exists() && f.length() > size) {
                                        rotate = true;
                                    }
                                    break;

                                case DATE:
                                // not implemented yet...
                                // if (creationdate > rotation date)
                                //  rotate=true;
                            }

                            // just pausing....
                            Thread.sleep(checkRotationInterval);
                        } catch (java.lang.InterruptedException e) {
                            // if we get an error the FileDumper
                            // must stop...
                            status = Status.FAILURE;
                            Log.serviceLog(e);
                        }
                    }
                }
            };
            
            // starting the checker...
            t = new Thread(r);
            t.setName("RotationChecker");
            // this thread cannot block 
            // a whole application
            t.setDaemon(true);
            t.start();
            
        //} catch (DumperException e) {
        //	throw new ObjectInitializationException(e);
        } catch (Exception e) {
            throw new ObjectInitializationException(e);
        }

    }

    /**
     * Open this file and 
     * @throws DumperException
     */
    @Override
    protected void open() throws DumperException {
        try {
            //ch = new RandomAccessFile(f, "rw").getChannel();
            FileOutputStream fos = new FileOutputStream(f,true);
            ch = fos.getChannel();
            //bw = new BufferedWriter(Channels.newWriter(ch, Constant.DEFAULT_ENCODING));
            synchronized(this){
            	bw = new BufferedWriter(Channels.newWriter(ch, Constant.DEFAULT_ENCODING));
            }
            
            // acquire lock
            lock = ch.tryLock();
            
            
            
            
        } catch (IOException e) {
            throw new DumperException(e);
        }
    }

    /**
     * 
     * @param e Anything extending Entry
     * @throws DumperException
     */
    @Override
    protected void write(Entry e) throws DumperException {
        try {
        	
            // dumping entry to file
            bw.append(e.getLine());
            bw.flush();

            // check if we need to rotate
            if (rotate) {
                this.rotateLog();
            }
        } catch (IOException ex) {
            throw new DumperException(ex);
        }
    }

    /**
     * Flush and close the BufferedWriter used to write to file
     */
    @Override
    protected void close() throws DumperException {
        try {
            // basic closing...
            // but we first flush it...
            bw.flush();
            bw.close();

        } catch (IOException e) {
            throw new DumperException(e);
        }
    }

    /**
     * Rotate our log file
     * @throws IOException If something went wrong rotating file
     */
    protected synchronized final void rotateLog() throws IOException, DumperException {
        // don't be scared by this, final result will be: filename.log20080610180512
        String oldLog = url + new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());

        // first we close the file
        bw.flush();
        bw.close();
        // then it's time to rename file to a new destination
        // and check if it went fine
        if (!f.renameTo(new File(oldLog))) {
            throw new IOException("Couldn't do rotation by size");
        }
        //Log.serviceLog(t.getName());
        
        // then we re-open the writer
        this.open();

        //this.isOpen=false;
        // if keep alive true we need to reopen the file
        // otherwise this is done normally in postprepare()
        //if (this.isKeepAlive()) {
        //	this.open();
        //}
        // set to default again...
        rotate = false;

        //notifyAll();
    }
}




